package br.ufc.ck0017.canon;

import br.ufc.ck0017.ir.tree.*;
import br.ufc.ck0017.temp.*;

import java.util.List;

/**
 * Created by Marcel on 23/06/2015.
 */
public class Canon {
    static boolean isNop(IRNode a) {
        return (a instanceof EXP) && ((EXP)a).expression instanceof CONST;
    }

    static IRNode seq(IRNode a, IRNode b) {
        if (isNop(a)) return b;
        else if (isNop(b)) return a;
        else return new SEQ(a,b);
    }

    static boolean commute(IRNode a, IRNode b) {
        return isNop(a)
                || b instanceof NAME
                || b instanceof CONST;
    }

    static IRNode do_stm(SEQ s) {
        return seq(do_stm(s.left), do_stm(s.right));
    }

    static List<IRNode> kids; // isso aqui só pra compilar

    static IRNode do_stm(MOVE s) {
        if (s.destination instanceof TEMP  && s.source instanceof CALL) {
            return reorder_stm(new MoveCall((TEMP) s.destination, (CALL) s.source).build(kids)); // só pra compilar
        } else if (s.destination instanceof ESEQ) {
            return do_stm(IR.SEQ(((ESEQ) s.destination).statement, IR.MOVE(((ESEQ) s.destination).expression, s.source)));
        } else {
            return reorder_stm(s);
        }
    }

    static IRNode do_stm(EXP s) {
        if (s.expression instanceof CALL) {
            return reorder_stm(new ExpCall((CALL) s.expression).build(kids));
        } else {
            return reorder_stm(s);
        }
    }

    static IRNode do_stm(IRNode s) {
        if (s instanceof SEQ) return do_stm((SEQ)s);
        else if (s instanceof MOVE) return do_stm((MOVE)s);
        else if (s instanceof EXP) return do_stm((EXP)s);
        else return reorder_stm(s);
    }

    static ExpList lista_kids;
    static IRNode reorder_stm(IRNode s) {
        StmExpList x = reorder(lista_kids);
        return seq(x.stm, s.build(x.exps));
    }

    static ESEQ do_exp(ESEQ e) {
        IRNode stms = do_stm(e.statement);
        ESEQ b = do_exp(e.expression);
        return new ESEQ(seq(stms,b.statement), b.expression);
    }

    static ESEQ do_exp (IRNode e) {
        if (e instanceof ESEQ) return do_exp((ESEQ)e);
        else return reorder_exp(e);
    }

    static ESEQ reorder_exp (IRNode e) {
        IRNode st = null;
        ESEQ eseq = new ESEQ(st,e);

        // StmExpList x = reorder(e.build(kids));   Alguns problemas aqui
        // return new ESEQ(x.stm, e.build(x.exps));
        return eseq;


    }

    static StmExpList nopNull = new StmExpList(IR.EXP(IR.CONST(0)),null);

    static StmExpList reorder(ExpList exps) {
        IRNode s = null;
        List<IRNode> exp = null;
        StmExpList sl = new StmExpList(s,exp);
        if (exps==null) return nopNull;
        else {
            IRNode a = exps.head;
            if (a instanceof CALL) {
                Temp t = new Temp();
                IRNode e = IR.ESEQ(IR.MOVE(IR.TEMP(t), a),
                        IR.TEMP(t));
               // return reorder(new ExpList(e, exps.tail));
            } else {
                ESEQ aa = do_exp(a);
                StmExpList bb = reorder(exps.tail);
                if (commute(bb.stm, aa.expression)) {
                    /*return new StmExpList(seq(aa.statement,bb.stm),
                            new ExpList(aa.expression,bb.exps));  erro devido incompatililidade de classes*/
                }else {
                    Temp t = new Temp();
                    /*return new StmExpList(
                            seq(aa.statement,
                                    seq(new MOVE(new TEMP(t),aa.expression),
                                            bb.stm)),
                            new ExpList(new TEMP(t), bb.exps)); erro devido incompatibilidade de classes*/


                }
            }
        }
        return sl;
    }

    static StmList linear(SEQ s, StmList l) {
        return linear(s.left,linear(s.right,l));
    }
    static StmList linear(IRNode s, StmList l) {
        if (s instanceof SEQ) return linear((SEQ)s, l);
        else return new StmList(s,l);
    }

    static public StmList linearize(IRNode s) {
        return linear(do_stm(s), null);
    }

}
